<!DOCTYPE html>
<html lang="cn">

<head>
  <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Docker概要 镜像 操作系统分为内核空间和用户空间。对于Linux，内核启动后，会挂载root文件系统为其提供用户空间支持。而Docker中的镜像(Image)，就相当于是一个root文件系统，只是它有些特殊，除了提供容器运行时所需的程序、库、资源、配置等文件外，还会包含一些为运行时准备的配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据，其内容在构建之后不会改变。
Docker使用Union FS技术，将镜像设计为分层存储的架构。构建时，会一层层构建，前一层是后一层的基础，每层构建完就不会改变，后一层的任何改变只是发生在自己这一层。例如，删除前一层的文件并不是真正的删除，而只是在本层中标记上一层的文件是删除的。
获取镜像 使用如下命令可以从镜像仓库中拉取镜像:
docker pull [选项] [镜像仓库的地址[:端口号]/]仓库名[:标签] 镜像仓库的地址默认为Docker Hub的地址，对于Docker Hub，仓库名默认为library，所以我们往往可以简写为docker pull ubuntu:18.04。
列出镜像 我们可以这样列出全部的镜像: PS C:\Users\hejl&gt; docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hjlarry/cheers2019 latest f0c36061dc59 3 hours ago 4.01MB &lt;none&gt; &lt;none&gt; 1487cd0b23aa 3 hours ago 356MB ubuntu 18.04 ccc6e87d482b 4 days ago 64.2MB ubuntu latest ccc6e87d482b 4 days ago 64.2MB golang 1.11-alpine e116d2efa2ab 5 months ago 312MB gcr.azk8s.cn/google_containers/hyperkube-amd64 v1.9.2 583687acb4de 2 years ago 618MB 我们可以看到镜像ID是镜像的唯一标识，但标签可以有多个，例如ubuntu的18."><meta property="og:title" content="Docker核心概要" />
<meta property="og:description" content="" />
<meta property="og:type" content="website" />
<meta property="og:url" content="/docs/other/docker/" />

<title>Docker核心概要 | Home</title>
<link rel="icon" href="/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/book.min.63eb88daa545365405ecdbb21033286a325c60a36cfa6d22d21e7c3bc9286941.css" integrity="sha256-Y&#43;uI2qVFNlQF7NuyEDMoajJcYKNs&#43;m0i0h58O8koaUE=">
<script defer src="/cn.search.min.8d2462c3b745732864d843e8e2e7782a2ec838ca90fb94676baa461536bdae11.js" integrity="sha256-jSRiw7dFcyhk2EPo4ud4Ki7IOMqQ&#43;5Rna6pGFTa9rhE="></script>
<link rel="alternate" type="application/rss+xml" href="/docs/other/docker/index.xml" title="Home" />
<!--
Made with Book Theme
https://github.com/alex-shpak/hugo-book
-->

  
</head>

<body>
  <input type="checkbox" class="hidden" id="menu-control" />
  <main class="container flex">
    <aside class="book-menu">
      
  <nav>
<h2 class="book-brand">
  <a href="/"><img src="/logo.png" alt="Logo" /><span>Home</span>
  </a>
</h2>


<div class="book-search">
  <input type="text" id="book-search-input" placeholder="搜索" aria-label="搜索" maxlength="64" data-hotkeys="s/" />
  <div class="book-search-spinner spinner hidden"></div>
  <ul id="book-search-results"></ul>
</div>











  <ul>
<li><strong>计算机基础</strong>
<ul>
<li><a href="/docs/sicp/hardware/">硬件</a></li>
<li><a href="/docs/sicp/software/">软件</a></li>
<li><a href="/docs/sicp/program/">程序</a></li>
<li><a href="/docs/sicp/asm/">汇编</a></li>
</ul>
</li>
<li><strong>GO语言</strong>
<ul>
<li><a href="/docs/go/map/">字典</a></li>
<li><a href="/docs/go/closure/">闭包</a></li>
<li><a href="/docs/go/defer/">延迟调用</a></li>
<li><a href="/docs/go/goroutine/">并发调度</a></li>
<li><a href="/docs/go/alloc/">内存分配</a></li>
<li><a href="/docs/go/gc/">垃圾回收</a></li>
<li><a href="/docs/go/lock/">锁</a></li>
</ul>
</li>
<li><strong>Python语言</strong>
<ul>
<li><a href="/docs/python/memory/">内存管理</a></li>
<li><a href="/docs/python/interpreter/">解释器</a></li>
<li><a href="/docs/python/tools/">技巧工具</a></li>
</ul>
</li>
<li><strong>Mysql</strong>
<ul>
<li><a href="/docs/mysql/query/">查询</a></li>
<li><a href="/docs/mysql/theory/">原理</a></li>
</ul>
</li>
<li><strong>其他</strong>
<ul>
<li><a href="/docs/other/git/">git</a></li>
<li><a href="/docs/other/docker/"class=active>docker</a></li>
<li><a href="/docs/other/raft/">raft</a></li>
<li><a href="/docs/other/shell/">shell</a></li>
<li><a href="/docs/other/oop/">面向对象</a></li>
<li><a href="/docs/other/protocol/">网络协议</a></li>
<li><a href="/docs/other/tools/">系统工具</a></li>
</ul>
</li>
</ul>










</nav>




  <script>(function(){var menu=document.querySelector("aside.book-menu nav");addEventListener("beforeunload",function(event){localStorage.setItem("menu.scrollTop",menu.scrollTop);});menu.scrollTop=localStorage.getItem("menu.scrollTop");})();</script>


 
    </aside>

    <div class="book-page">
      <header class="book-header">
        
  <div class="flex align-center justify-between">
  <label for="menu-control">
    <img src="/svg/menu.svg" class="book-icon" alt="Menu" />
  </label>

  <strong>Docker核心概要</strong>

  <label for="toc-control">
    <img src="/svg/toc.svg" class="book-icon" alt="Table of Contents" />
  </label>
</div>


  
    <input type="checkbox" class="hidden" id="toc-control" />
    <aside class="hidden clearfix">
      
  <nav id="TableOfContents">
  <ul>
    <li><a href="#镜像">镜像</a>
      <ul>
        <li><a href="#获取镜像">获取镜像</a></li>
        <li><a href="#列出镜像">列出镜像</a></li>
        <li><a href="#删除镜像">删除镜像</a></li>
        <li><a href="#定制镜像">定制镜像</a></li>
        <li><a href="#构建镜像">构建镜像</a></li>
      </ul>
    </li>
    <li><a href="#容器">容器</a>
      <ul>
        <li><a href="#创建容器">创建容器</a></li>
        <li><a href="#启动容器">启动容器</a></li>
        <li><a href="#新建并启动">新建并启动</a></li>
        <li><a href="#后台运行">后台运行</a></li>
        <li><a href="#查看所有容器">查看所有容器</a></li>
        <li><a href="#进入容器">进入容器</a></li>
        <li><a href="#终止和删除">终止和删除</a></li>
      </ul>
    </li>
    <li><a href="#数据管理">数据管理</a>
      <ul>
        <li><a href="#数据卷">数据卷</a></li>
        <li><a href="#挂载主机目录">挂载主机目录</a></li>
      </ul>
    </li>
    <li><a href="#网络">网络</a></li>
    <li><a href="#docker-compose">Docker Compose</a>
      <ul>
        <li><a href="#主要命令">主要命令</a></li>
        <li><a href="#模板文件">模板文件</a></li>
      </ul>
    </li>
  </ul>
</nav>


    </aside>
  
 
      </header>

      
      
  <article class="markdown"><h1 id="docker概要">Docker概要</h1>
<h2 id="镜像">镜像</h2>
<p>操作系统分为内核空间和用户空间。对于Linux，内核启动后，会挂载root文件系统为其提供用户空间支持。而Docker中的镜像(Image)，就相当于是一个root文件系统，只是它有些特殊，除了提供容器运行时所需的程序、库、资源、配置等文件外，还会包含一些为运行时准备的配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据，其内容在构建之后不会改变。</p>
<p>Docker使用<a href="https://en.wikipedia.org/wiki/Union_mount">Union FS</a>技术，将镜像设计为分层存储的架构。构建时，会一层层构建，前一层是后一层的基础，每层构建完就不会改变，后一层的任何改变只是发生在自己这一层。例如，删除前一层的文件并不是真正的删除，而只是在本层中标记上一层的文件是删除的。</p>
<h3 id="获取镜像">获取镜像</h3>
<p>使用如下命令可以从镜像仓库中拉取镜像:</p>
<pre><code>docker pull [选项] [镜像仓库的地址[:端口号]/]仓库名[:标签]
</code></pre><p>镜像仓库的地址默认为Docker Hub的地址，对于Docker Hub，仓库名默认为library，所以我们往往可以简写为<code>docker pull ubuntu:18.04</code>。</p>
<h3 id="列出镜像">列出镜像</h3>
<p>我们可以这样列出全部的镜像:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker image ls
REPOSITORY                                       TAG                 IMAGE ID            CREATED             SIZE
hjlarry/cheers2019                               latest              f0c36061dc59        <span style="color:#ae81ff">3</span> hours ago         4.01MB
&lt;none&gt;                                           &lt;none&gt;              1487cd0b23aa        <span style="color:#ae81ff">3</span> hours ago         356MB
ubuntu                                           18.04               ccc6e87d482b        <span style="color:#ae81ff">4</span> days ago          64.2MB
ubuntu                                           latest              ccc6e87d482b        <span style="color:#ae81ff">4</span> days ago          64.2MB
golang                                           1.11-alpine         e116d2efa2ab        <span style="color:#ae81ff">5</span> months ago        312MB
gcr.azk8s.cn/google_containers/hyperkube-amd64   v1.9.2              583687acb4de        <span style="color:#ae81ff">2</span> years ago         618MB</code></pre></div>
我们可以看到镜像ID是镜像的唯一标识，但标签可以有多个，例如ubuntu的18.04和latest是同一个镜像。</p>
<p>镜像的体积可能会比Docker Hub中显示的要大一些，因为Docker Hub进行了一定的压缩。实际这些镜像占用的总空间也不是把他们加起来就能算出来，因为共同的层可以复用。镜像占用的总大小可以通过<code>docker system df</code>看到。</p>
<p>有一种特殊的镜像，镜像名和标签均为<!-- raw HTML omitted -->，被称为虚悬镜像(dangling image)。它们没有什么用，可以通过<code>docker image prune</code>一键删除。</p>
<p>还有一种镜像叫中间层镜像，用来加速构建、重复利用资源，有的中间层镜像也没有标签和名称，它们不能被删除。可以通过<code>docker image ls -a</code>观察到包含中间层镜像的所有镜像。</p>
<h3 id="删除镜像">删除镜像</h3>
<p>使用<code>docker image rm &lt;镜像1&gt; [&lt;镜像2&gt; ...]</code>命令可删除镜像。这里既可以用镜像的名称，也就是<code>&lt;仓库名&gt;:&lt;标签&gt;</code>来表示镜像；也可以用镜像的ID，一般取前3位，足以区分出别的镜像即可。</p>
<p>在删除镜像时，我们往往会看到删除信息中既有<code>Untagged</code>，也有<code>Deleted</code>。<code>Untagged</code>实际上是因为我们要删除的是某个tag标签下的镜像，需要去取消标签。</p>
<p>可以使用多个命令配合来批量删除一些镜像，例如<code>docker image rm $(docker image ls -q redis)</code>可以删除所有仓库名为redis的镜像，<code>docker image rm $(docker image ls -q -f before=mongo:3.2)</code>可以删除所有mongo版本3.2之前的镜像。</p>
<h3 id="定制镜像">定制镜像</h3>
<p>镜像的定制实际上就是定制每层所添加的配置和文件，我们可以把每一层的修改、安装、构建、操作的命令都写入一个Dockerfile，通过它来构建、定制镜像。</p>
<h4 id="from">FROM</h4>
<p>用来指定基础镜像。定制镜像一定是以一个镜像为基础，在其上进行定制，FROM是必备的且必须是第一条指令。</p>
<p>我们一般可以用一些服务类的镜像作为基础镜像，例如nginx、redis、python、golang等，也可以使用更为基础的操作系统镜像，如ubuntu、centos、alpine等。还以为用scratch作为基础镜像，它表示一个空白的镜像，接下来所写的指令为镜像的第一层，因为有一些静态编译的程序并不需要操作系统提供运行时支持。</p>
<h4 id="run">RUN</h4>
<p>用来执行命令行命令的，可以像shell脚本一样使用，但我们不应该把每个命令都去对应一个RUN，例如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> debian:stretch</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt-get update<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> apt-get install -y gcc libc6-dev make wget<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> wget -O redis.tar.gz <span style="color:#e6db74">&#34;http://download.redis.io/releases/redis-5.0.3.tar.gz&#34;</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> mkdir -p /usr/src/redis<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> tar -xzf redis.tar.gz -C /usr/src/redis --strip-components<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> make -C /usr/src/redis<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> make -C /usr/src/redis install</code></pre></div>
这样构建的镜像非常臃肿，添加了很多运行时不需要的东西，增加了构建部署的时间，也容易出错。应该这样写:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> debian:stretch</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> buildDeps<span style="color:#f92672">=</span><span style="color:#e6db74">&#39;gcc libc6-dev make wget&#39;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> apt-get update <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> apt-get install -y $buildDeps <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> wget -O redis.tar.gz <span style="color:#e6db74">&#34;http://download.redis.io/releases/redis-5.0.3.tar.gz&#34;</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> mkdir -p /usr/src/redis <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> tar -xzf redis.tar.gz -C /usr/src/redis --strip-components<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span> <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> make -C /usr/src/redis <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> make -C /usr/src/redis install <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm -rf /var/lib/apt/lists/* <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm redis.tar.gz <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> rm -r /usr/src/redis <span style="color:#ae81ff">\
</span><span style="color:#ae81ff"></span>    <span style="color:#f92672">&amp;&amp;</span> apt-get purge -y --auto-remove $buildDeps</code></pre></div>
所有的命令都是一个目的，即编译、安装redis可执行文件，没必要多层。此外，这组命令的最后添加了清理工作的命令，删除为了编译构建所需的文件，清理了所有下载、展开的文件，还清理了apt的缓存文件。镜像构建时，一定要确保每一层只添加真正需要添加的东西，任何无关的东西都应该清理掉。</p>
<h4 id="copy">COPY</h4>
<p>该指令从构建上下文的目录中复制文件到镜像内的目标路径位置，源路径可以是多个，也可以用通配符，通配符规则是Go的<a href="https://golang.org/pkg/path/filepath/#Match">filepath.Match</a>规则。还可以通过添加<code>--chown=&lt;user&gt;:&lt;group&gt;</code>选项来改变文件的所属用户和组。例如:</p>
<pre><code>COPY hom* /mydir/
COPY hom?.txt /mydir/
COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
</code></pre><h4 id="add">ADD</h4>
<p>该命令和COPY的格式和性质基本一致，按最佳实践，COPY的语义更加明确应尽可能的使用，尽在需要自动解压缩的场合使用ADD。</p>
<h4 id="cmd">CMD</h4>
<p>因为容器是进程，那么在启动容器的时候，就需要指定所运行的程序及参数，CMD就用来指定默认的容器主进程的启动命令。比如，ubuntu镜像的CMD设置的是/bin/bash，所以我们运行容器<code>docker run -it ubuntu</code>会进入到bash，但当我们使用<code>docker run -it ubuntu cat /etc/os-release</code>运行容器时就用<code>cat /etc/os-release</code>替换掉了<code>/bin/bash</code>命令。</p>
<p>该指令既支持shell格式，<code>CMD &lt;命令&gt;</code>，也支持exec格式，<code>CMD [&quot;可执行文件&quot;, &quot;参数1&quot;, &quot;参数2&quot;...]</code>。但是shell格式会被包装一个<code>sh -c</code>的参数。例如<code>CMD echo $HOME</code>在实际的执行时会被替换为<code>CMD [ &quot;sh&quot;, &quot;-c&quot;, &quot;echo $HOME&quot; ]</code>。</p>
<p>另外，docker中的应用都是以前台执行的，而不是像虚拟机、物理机那样，可以用systemd去启动一个后台服务，容器内没有后台服务的概念。类似于<code>CMD service nginx start</code>会发现容器执行后就立即退出了，甚至<code>systemctl</code>命令结果却根本执行不了，因为容器就是为了主进程而存在的，主进程退出容器就没有存在的意义也会退出，这条命令会被翻译为<code>CMD [ &quot;sh&quot;, &quot;-c&quot;, &quot;service nginx start&quot;]</code>，sh是主进程，当<code>service nginx start</code>命令结束后，sh就结束了，主进程退出自然容器就会退出。正确的做法是直接执行nginx可执行文件并以前台形式运行，如<code>CMD [&quot;nginx&quot;, &quot;-g&quot;, &quot;daemon off;&quot;]</code>。</p>
<h4 id="env">ENV</h4>
<p>用来设置环境变量，设置后无论是之后的指令，还是运行时的应用都可以直接使用定义的环境变量。支持格式:</p>
<ul>
<li>ENV <!-- raw HTML omitted --> <!-- raw HTML omitted --></li>
<li>ENV <!-- raw HTML omitted -->=<!-- raw HTML omitted --> <!-- raw HTML omitted -->=<!-- raw HTML omitted --> &hellip;</li>
</ul>
<p>例如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">ENV</span> VERSION<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>.0 DEBUG<span style="color:#f92672">=</span>on NAME<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Happy Feet&#34;</span><span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> curl -SLO <span style="color:#e6db74">&#34;https://nodejs.org/dist/v</span>$VERSION<span style="color:#e6db74">/node-v</span>$NAME<span style="color:#e6db74">-linux-x64.tar.xz&#34;</span></code></pre></div></p>
<h4 id="arg">ARG</h4>
<p>和ENV类似，也是设置环境变量，只是它在容器运行时不会存在这些环境变量。</p>
<h4 id="volume">VOLUME</h4>
<p>因为容器的运行应保持容器存储层不发生写操作，那么对于数据库类需要动态保存数据的应用，其数据库文件应保存于卷(volume)中。该指令用于挂载一个目录为匿名卷，如果容器运行时用户未指定匿名卷，就会用它做匿名卷，向其写入数据即可避免向容器存储层写入大量数据；如果用户指定，则会覆盖掉这个指令中的设置。</p>
<p>其格式为<code>VOLUME &lt;路径&gt;</code>或<code>VOLUME [&quot;&lt;路径1&gt;&quot;, &quot;&lt;路径2&gt;&quot;...]</code>。用户指定命名卷可以这样写<code>docker run -d -v mydata:/data xxxx</code>，即把mydata这个命名卷挂载到了/data位置，会替代Dockerfile中定义的匿名卷挂载配置。</p>
<h4 id="expose">EXPOSE</h4>
<p>只是一个声明，容器运行时提供的服务端口。容器不会因为这个声明就自动开启这个端口的服务，只是帮助镜像使用者理解，或是使用<code>docker run -P</code>做随机端口映射时可自动映射到该指令设置的端口。</p>
<h4 id="workdir">WORKDIR</h4>
<p>使用该指令可以指定当前目录(或者称为工作目录)，以后各层的当前目录就被设置为它，如该目录不存在则会自动创建该目录。</p>
<p>比如我们可能会这样写:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-dockerfile" data-lang="dockerfile"><span style="color:#66d9ef">RUN</span> cd /app<span style="color:#960050;background-color:#1e0010">
</span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> echo <span style="color:#e6db74">&#34;hello&#34;</span> &gt; world.txt</code></pre></div></p>
<p>这样构建运行后，会发现找不到/app/world.txt这个文件，因为两个RUN代表两层，它们的执行环境不同，运行到第二层时启动的是一个全新的容器。这个时候就应该用<code>WORKDIR</code>指令进行设置。</p>
<h4 id="user">USER</h4>
<p>使用该指令可以切换到指定的用户，其后的每一层执行RUN、CMD以及ENTRYPOINT之类命令都会是这个新的身份。这个用户必须是事先建立好的。</p>
<h3 id="构建镜像">构建镜像</h3>
<p>使用<code>docker build [选项] &lt;上下文路径/URL/-&gt;</code>命令可进行镜像构建。</p>
<h4 id="上下文">上下文</h4>
<p>我们经常使用<code>docker build .</code>，往往会理解为<code>.</code>表示当前目录，下意识的认为这是dockerfile的所在路径，但这么理解是不准确的，实际上这是在指定上下文路径。</p>
<p>那么什么是上下文呢？Docker在运行时分为服务端守护进程和客户端工具，我们输入docker相关命令都是客户端操作，通过<a href="https://docs.docker.com/develop/sdk/">Docker Remote API</a>与服务端docker引擎交互。当我们进行镜像构建时，就是通过<code>docker build</code>在服务端进行构建，上下文路径中的内容会被发送过去，这个<code>.</code>就是在指定上下文路径，这样当在dockerfile中遇到文件复制这样的语句，例如<code>COPY ./package.json /app/</code>时并不是要复制dockerfile下的package.json，而是去复制上下文路径下的package.json。所以类似<code>COPY ../package.json /app/</code>或者<code>COPY /opt/*** /app</code>都超出了上下文路径而无效，如果需要它们的话就得把它们复制到上下文目录中去。</p>
<p>dockerfile一般放在一个空目录或项目的根目录下，如果有东西不希望发送给docker引擎，可以使用<code>.dockerignore</code>剔除掉。默认情况下，会将上下文目录下的名为<code>Dockerfile</code>的文件作为Dockerfile，实际上可以通过如<code>-f ../Dockerfile.php</code>指定某个文件为Dockerfile。</p>
<h4 id="其他构建方式">其他构建方式</h4>
<p>还可以直接使用git repo的方式构建:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt;  docker build https://github.com/twang2218/gitlab-ce-zh.git#:11.1
Sending build context to Docker daemon  6.144kB
Step 1/23 : FROM gitlab/gitlab-ce:11.1.4-ce.0 as builder
11.1.4-ce.0: Pulling from gitlab/gitlab-ce
8ee29e426c26: Pull complete                                                                    6e83b260b73b: Pull complete                                                                    e26b65fd1143: Pull complete      
...</code></pre></div>
该命令指定了构建所需的git repo，指定了默认的master分支，构建目录为<code>/11.1/</code>，然后docker会自己去clone项目切换到指定分支，并进入指定目录进行构建。</p>
<p>也可以通过tar压缩包构建，例如<code>docker build http://url/context.tar.gz</code>，会去下载tar压缩包并自动解压缩，以其为上下文，开始构建。</p>
<h2 id="容器">容器</h2>
<p>镜像是静态的定义，而容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。</p>
<p>容器的实质是进程，但与直接在宿主执行的进程是不同的，容器进程运行于属于自己独立的命名空间，有自己的root文件系统、网络配置、进程空间、用户ID空间等，也就是它是一个隔离的环境。</p>
<p>每个容器运行时，是以镜像为基础层，在其上创建一个当前容器的存储层用于容器运行时读写，该存储层的生命周期和容器是一样的。容器存储层应保持无状态化，所有对文件的写入操作都应该使用数据卷或绑定宿主目录，在这些位置的读写就会跳过容器存储层，直接对宿主发生读写，其性能和稳定性更高。</p>
<h3 id="创建容器">创建容器</h3>
<p>使用<code>docker create &lt;image&gt;</code>可以基于镜像创建一个容器，创建后其处于停止状态，需要用启动命令启动。
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\W</span>indows<span style="color:#ae81ff">\s</span>ystem32&gt; docker create -it ubuntu:latest
1179ee64f25b6ddbe95e916c4b888ff4d05878ebabd77c94658c9cbc8686b360
PS C:<span style="color:#ae81ff">\W</span>indows<span style="color:#ae81ff">\s</span>ystem32&gt; docker start -i <span style="color:#ae81ff">117</span>
root@1179ee64f25b:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var</code></pre></div></p>
<h3 id="启动容器">启动容器</h3>
<p>使用<code>docker start &lt;container_id/container_name&gt;</code>可以将一个终止状态的容器启动运行。
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\W</span>indows<span style="color:#ae81ff">\s</span>ystem32&gt; docker container start -i f8b
root@f8be09e399ae:/# ps
  PID TTY          TIME CMD
    <span style="color:#ae81ff">1</span> pts/0    00:00:00 bash
   <span style="color:#ae81ff">10</span> pts/0    00:00:00 ps
root@f8be09e399ae:/# exit
exit</code></pre></div>
使用<code>ps</code>或<code>top</code>可以查看进程信息，说明容器中仅运行了默认的bash应用。</p>
<h3 id="新建并启动">新建并启动</h3>
<p>使用<code>docker run</code>命令，相当于<code>docker create</code>+<code>docker start</code>，例如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker run -i -t ubuntu:latest
root@f8be09e399ae:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var</code></pre></div></p>
<p><code>-t</code>选项让docker分配一个伪终端来绑定到容器的标准输入上，<code>-i</code>选项让容器的标准输入保持打开。</p>
<p>当执行<code>docker run</code>时，Docker后台实际上运行了这些操作:</p>
<ol>
<li>检查本地是否存在指定的镜像，不存在就从公有仓库下载</li>
<li>利用镜像创建并启动一个容器</li>
<li>分配一个文件系统，并在只读的镜像层外面挂载一层可读写层</li>
<li>从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去</li>
<li>从地址池配置一个ip地址给容器</li>
<li>执行用户指定的应用程序</li>
<li>执行完毕后容器被终止</li>
</ol>
<h3 id="后台运行">后台运行</h3>
<p>很多时候，需要让Docker在后台运行而不是直接把执行命令的结果输出在当前宿主机上，此时可以通过添加<code>-d</code>参数来实现。<code>docker run</code>和<code>docker create</code>命令都可以使用该参数。</p>
<p>但容器是否会长久运行，只取决于<code>docker run</code>到底跑了什么进程，和<code>-d</code>参数无关。如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker run -d ubuntu:18.04 /bin/sh -c <span style="color:#e6db74">&#34;while true; do echo hello world; sleep 1; done&#34;</span>
6ec4042f4d06f94a46aadebe8d05f33d9916985b19bae6d8d63ed867d89b3714
PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker logs 6ec
hello world
hello world
hello world
...</code></pre></div>
使用<code>docker logs</code>可以看到某后台运行的容器的输出信息。</p>
<h3 id="查看所有容器">查看所有容器</h3>
<p>使用<code>docker ps</code>可以看到正在运行的容器，<code>docker ps -a</code>会看到包括终止状态的容器，它和<code>docker container ls -a</code>等价。</p>
<h3 id="进入容器">进入容器</h3>
<p>对于后台运行的容器，有时需要进入容器进行操作，可以使用<code>docker exec</code>或<code>docker attach</code>命令，但attach进入后如果输入<code>exit</code>退出容器会导致容器状态停止，所以更推荐<code>exec</code>的方式。
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-sh" data-lang="sh">PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker exec -it 6f8 bash
root@6f8a756d46fe:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@6f8a756d46fe:/# exit
exit
PS C:<span style="color:#ae81ff">\U</span>sers<span style="color:#ae81ff">\h</span>ejl&gt; docker attach 6f8
root@6f8a756d46fe:/#    </code></pre></div></p>
<h3 id="终止和删除">终止和删除</h3>
<p>使用<code>docker stop &lt;container_id/container_name&gt;</code>可停止运行中的容器。</p>
<p>使用<code>docker rm &lt;container_id/container_name&gt;</code>可删除，添加<code>-f</code>参数会强制删除即使该容器正在运行。</p>
<p>使用<code>docker container prune</code>可以清理掉所有处于终止状态的容器。</p>
<h2 id="数据管理">数据管理</h2>
<p>在docker内部及容器之间管理数据主要有两种方式，即通过数据卷和挂载主机目录。</p>
<h3 id="数据卷">数据卷</h3>
<p>数据卷是一个特殊的目录，它会绕过Union FS，提供一些有用的特性:</p>
<ul>
<li>可以在容器之间共享和重用</li>
<li>对数据卷的修改会立即生效</li>
<li>对数据卷的更新不会影响镜像</li>
<li>数据卷一直存在，即使容器被删除</li>
</ul>
<p>可以通过<code>docker volume create my-vol</code>创建一个数据卷，然后使用<code>docker run -it -v my-vol:/webapp  ubuntu</code>来将创建的数据卷挂载到容器中，可同时挂载多个数据卷。</p>
<h3 id="挂载主机目录">挂载主机目录</h3>
<p>也可以直接将本地主机的一个目录挂载在容器中，本地目录必须是一个绝对路径，挂载后默认是可读写权限，可在挂载时设置为只读。例<code>docker run -it -v C:\Users\hejl:/mytt:ro ubuntu</code>。</p>
<h2 id="网络">网络</h2>
<p>启动容器时如果不指定参数，则在容器外部无法通过网络来访问容器内的网络应用和服务。</p>
<p>可以使用<code>-P</code>参数，大写P使得Docker会随机映射一个49000~49900的端口作为容器的开放网络端口。</p>
<p>也可通过<code>-p</code>小写p指定端口，如<code>docker run -p 5000:5000 -p 3000:80 ubuntu</code>。</p>
<h2 id="docker-compose">Docker Compose</h2>
<p>Compose的定位是定义和运行多个Docker容器的应用，其中有两个重要的概念:</p>
<ul>
<li>服务(service): 一个应用的容器，实际上可以包括若干运行相同镜像的容器实力。</li>
<li>项目(project): 由一组关联的应用容器组成的一个完整业务单元, 在<code>docker-compose.yml</code>文件中定义。</li>
</ul>
<h3 id="主要命令">主要命令</h3>
<p>对于Compose来说，大部分命令的对象既可以是项目本身，也可以是项目中的服务，如不特别说明，命令的对象就是项目，也就是说项目中的所有服务都会受到命令的影响。</p>
<h4 id="build">build</h4>
<p>用于构建或重新构建项目中的服务容器。</p>
<p>服务容器一旦构建后，将会带上一个标记名，例如对于web项目中的一个db容器，可能是web_db。</p>
<p>有一些额外选项:</p>
<ul>
<li>&ndash;force-rm，删除构建过程中的临时容器</li>
<li>&ndash;no-cache，构建镜像过程中不使用缓存</li>
<li>&ndash;pull，始终尝试通过pull来获取更新版本的镜像</li>
</ul>
<h4 id="config">config</h4>
<p>验证<code>docker-compose.yml</code>文件中的格式是否正确，如果正确会显示配置，错误则显示错误原因。</p>
<h4 id="exec">exec</h4>
<p>进入指定的容器，容器必须是运行状态的。例如<code>docker-compose exec web sh</code></p>
<h4 id="run-1">run</h4>
<p>用于在指定服务上运行一个命令。</p>
<p>类似于启动容器后运行指定的命令，相关的卷、链接等也会按照配置自动创建，如果存在关联的服务则关联的服务也会自动启动。但指定命令会覆盖掉原有的自动运行的命令，且不会自动创建端口以免冲突。</p>
<p>额外选项:</p>
<ul>
<li>-d，后台运行容器</li>
<li>&ndash;no-deps，不自动启动关联的容器</li>
<li>-e KEY=VAL，设置环境变量</li>
<li>&ndash;rm，运行命令后自动删除容器</li>
<li>&ndash;service-ports，配置服务端口并映射到本地主机</li>
</ul>
<p>例如: <code>docker-compose run --no-deps web python manage.py shell</code></p>
<h4 id="up">up</h4>
<p>它将尝试自动完成包括构建镜像、(重新)创建服务，启动服务，并关联服务相关容器的一系列操作。大部分时候可以通过该命令来启动一个项目。</p>
<p>默认情况下，启动的容器都在前台，会输出容器的输出信息便于调试。生产环境下建议后台启动。</p>
<p>额外选项:</p>
<ul>
<li>-d，后台运行容器</li>
<li>&ndash;no-deps，不启动服务链接的容器</li>
<li>&ndash;force-recreate，强制重新创建容器</li>
<li>&ndash;no-create，如果容器已经存在了，则不重新创建</li>
<li>-t，停止容器时的超时</li>
</ul>
<h3 id="模板文件">模板文件</h3>
<p>默认的文件名称为<code>docker-compose.yml</code>，下面主要介绍核心指令的含义。</p>
<p>每个服务都必须通过image指令或build指令来自动构建生成镜像，如果使用build，Dockerfile中设置的选项(如CMD、EXPOSE、VOLUME、ENV)将会自动被获取，无需在模板文件中重复设置。</p>
<h4 id="build-1">build</h4>
<p>指定Dockerfile所在文件夹的路径，可以是绝对路径或是相对于<code>docker-compose.yml</code>文件的相对路径。如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">version</span>: <span style="color:#e6db74">&#39;3&#39;</span>
<span style="color:#66d9ef">services</span>:

  <span style="color:#66d9ef">webapp</span>:
    <span style="color:#66d9ef">build</span>: ./dir</code></pre></div></p>
<p>也可以使用context指令指定Dockerfile所在的文件夹路径，使用dockerfile指令指定Dockerfile的文件名，使用arg指令指定构建镜像时的变量。如：
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">version</span>: <span style="color:#e6db74">&#39;3&#39;</span>
<span style="color:#66d9ef">services</span>:

  <span style="color:#66d9ef">webapp</span>:
    <span style="color:#66d9ef">build</span>:
      <span style="color:#66d9ef">context</span>: ./dir
      <span style="color:#66d9ef">dockerfile</span>: Dockerfile-alternate
      <span style="color:#66d9ef">args</span>:
        <span style="color:#66d9ef">buildno</span>: <span style="color:#ae81ff">1</span></code></pre></div></p>
<h4 id="command">command</h4>
<p>会覆盖掉容器启动后默认执行的命令。例如:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">version</span>: <span style="color:#e6db74">&#34;3&#34;</span>
<span style="color:#66d9ef">services</span>:

  <span style="color:#66d9ef">db</span>:
     <span style="color:#66d9ef">image</span>: mysql:<span style="color:#ae81ff">8.0</span>
     <span style="color:#66d9ef">command</span>:
      - --character-set-server=utf8mb4</code></pre></div>
没有command时只启动<code>mysqld</code>，加上以后变为<code>mysqld --character-set-server=utf8mb4</code></p>
<h4 id="depends_on">depends_on</h4>
<p>解决容器之间的依赖、启动先后问题。</p>
<h4 id="environment">environment</h4>
<p>设置环境变量，可以使用如下两种格式:
<div class="highlight"><pre style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4"><code class="language-yml" data-lang="yml"><span style="color:#66d9ef">environment</span>:
  <span style="color:#66d9ef">RACK_ENV</span>: development
  <span style="color:#66d9ef">SESSION_SECRET</span>:

<span style="color:#66d9ef">environment</span>:
  - RACK_ENV=development
  - SESSION_SECRET</code></pre></div>
在这里设置的环境变量，不能用于容器的构建过程中，只能在容器运行以后才能获取到。例如若dockerfie中有一层是<code>RUN flask db migrate</code>，则它无法获取到<code>docker-compose.yml</code>中设置的数据库连接的环境变量。正确的方法是dockerfile中没有这一层，而是通过<code>docker-compose run --rm web flask db migrate</code>来达到目的。</p>
<h4 id="expose-1">expose</h4>
<p>暴漏端口，但不映射到宿主机，只有被连接的服务能通过该端口访问。</p>
<h4 id="image">image</h4>
<p>指定为镜像名称或镜像ID，如果镜像在本地不存在，将会尝试拉取这个镜像。</p>
</article>
 
      

      <footer class="book-footer">
        
  <div class="flex justify-between">



  <div>
    
    <a class="flex align-center" href="https://github.com/hjlarry/hjlarry.github.io/commit/f8150d4d79b0bd5434db9dad6eea68562baf2245" title='最后修改者 hjlarry | April 21, 2020' target="_blank" rel="noopener">
      <img src="/svg/calendar.svg" class="book-icon" alt="Calendar" />
      <span>April 21, 2020</span>
    </a>
  </div>



  <div>
    <a class="flex align-center" href="https://github.com/hjlarry/hjlarry.github.io/edit/master/content/docs/other/docker/_index.md" target="_blank" rel="noopener">
      <img src="/svg/edit.svg" class="book-icon" alt="Edit" />
      <span>编辑本页</span>
    </a>
  </div>

</div>

 
        <br>
<div style="text-align: center;font-size:xx-small;">
    Powered by <a href="https://gohugo.io" target="_blank">Hugo</a> | Theme by <a
        href="https://github.com/alex-shpak/hugo-book" target="_blank">hugo-book</a>
</div>
      </footer>

      
  
 

      <label for="menu-control" class="hidden book-menu-overlay"></label>
    </div>

    
    <aside class="book-toc">
      
  <nav id="TableOfContents">
  <ul>
    <li><a href="#镜像">镜像</a>
      <ul>
        <li><a href="#获取镜像">获取镜像</a></li>
        <li><a href="#列出镜像">列出镜像</a></li>
        <li><a href="#删除镜像">删除镜像</a></li>
        <li><a href="#定制镜像">定制镜像</a></li>
        <li><a href="#构建镜像">构建镜像</a></li>
      </ul>
    </li>
    <li><a href="#容器">容器</a>
      <ul>
        <li><a href="#创建容器">创建容器</a></li>
        <li><a href="#启动容器">启动容器</a></li>
        <li><a href="#新建并启动">新建并启动</a></li>
        <li><a href="#后台运行">后台运行</a></li>
        <li><a href="#查看所有容器">查看所有容器</a></li>
        <li><a href="#进入容器">进入容器</a></li>
        <li><a href="#终止和删除">终止和删除</a></li>
      </ul>
    </li>
    <li><a href="#数据管理">数据管理</a>
      <ul>
        <li><a href="#数据卷">数据卷</a></li>
        <li><a href="#挂载主机目录">挂载主机目录</a></li>
      </ul>
    </li>
    <li><a href="#网络">网络</a></li>
    <li><a href="#docker-compose">Docker Compose</a>
      <ul>
        <li><a href="#主要命令">主要命令</a></li>
        <li><a href="#模板文件">模板文件</a></li>
      </ul>
    </li>
  </ul>
</nav>

 
    </aside>
    
  </main>

  
</body>

</html>












